﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace MidiAnalyzer
{
	public partial class frmMain : Form
	{
		MIDI _midi = null;

		public frmMain() => InitializeComponent();

		private void buttonOpenMidiFile_Click(object sender, EventArgs e)
		{
			DialogResult result = openFileDialog.ShowDialog();

			if (result == DialogResult.OK) {
				textboxMidiFile.Text = openFileDialog.FileName;

				openMidiFile(openFileDialog.FileName);
			}
		}

		private void openMidiFile(string filename)
		{
			FileStream fileStream = new FileStream(filename, FileMode.Open);
			BinaryReader binaryReader = new BinaryReader(fileStream);

			using (binaryReader) {
				_midi = new MIDI(binaryReader);
				_midi.AnalysisFile();
			}

			if (_midi.Header == MIDI.HEADER) {
				listviewMain.Items.Clear();
				textboxTrackData.Clear();
				textboxNoteData.Clear();

				appendListViewItem("Header", _midi.Header);
				appendListViewItem("HeaderLength", _midi.HeaderLength.ToString());
				appendListViewItem("MidiFormat", _midi.MidiFormatWithDescription);
				appendListViewItem("TrackCount", _midi.TrackCount.ToString());
				appendListViewItem("TicksPerQuarterNote", _midi.TicksPerQuarterNote.ToString());
				appendListViewItem("TrackHeader", _midi.TrackHeader_0);
				appendListViewItem("TrackLength", _midi.TrackLength_0.ToString());
				appendListViewItem("TrackData");

				_midi.AnalysisTrackData_0(this);
			} else {
				MessageBox.Show("not midi file");
			}
		}

		private void appendListViewItem(string property, string value = "")
		{
			ListViewItem item = new ListViewItem();

			item.SubItems.Add(new ListViewItem.ListViewSubItem() {Text = property});
			item.SubItems.Add(new ListViewItem.ListViewSubItem() {Text = value});

			listviewMain.Items.Add(item);
		}

		public void appendTrackDataOutput(string property, string value)
		{
			textboxTrackData.Text += string.Format("{0}: {1}\r\n", property, value);
		}

		public void appendNoteDataOutput(string note, int speed)
		{
			textboxNoteData.Text += string.Format("'{0}:{1}', ", note, (speed / 24).ToString());
		}

		private void frmMain_Load(object sender, EventArgs e)
		{
			if (textboxMidiFile.Text.ToLower().EndsWith(".mid")) {
				openMidiFile(textboxMidiFile.Text);
			}
		}

		class MIDI
		{
			private BinaryReader reader = null;

			private string _header = string.Empty;
			private int _header_length = -1;
			private int _midi_format = -1;
			private int _track_count = -1;
			private int _ticks_per_quarter_note = -1;
			private string[] _track_header = null;
			private int[] _track_length = null;
			private byte[][] _track_data = null;

			public const string HEADER = "MThd";
			public const string TRACK_HEADER = "MTrk";

			private readonly string[] MIDI_Format = {"SingleTrack", "MultiTrackSync", "MultiTrackAsync"};
			private readonly string[] NOTES = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#","B"};

			public MIDI(BinaryReader reader) => this.reader = reader;




			public int HeaderLength => _header_length;

			public string Header => _header;

			public int MidiFormat => _midi_format;

			public string MidiFormatWithDescription => MIDI_Format[_midi_format];

			public int TrackCount => _track_count;

			public int TicksPerQuarterNote => _ticks_per_quarter_note;

			public string TrackHeader_0 => _track_header[0];

			public int TrackLength_0 => _track_length[0];

			public byte[] TrackData_0 => _track_data[0];




			/*****************************************************************
			*	文件头格式说明
			*	4D 54 68 64 // MThd，MIDI头块
			*	00 00 00 06 // MThd头块大小，6字节
			*	------------
			*	00 00 // MIDI 0（零）型文件，所有音符在一个Track中
			*	00 01 // 轨道数，0型文件只有一个Track
			*	01 E0 // 所谓的TPQN，每个四分音符中有0x1E0（480）个MIDI Tick，
			*	------------
			*	4D 54 72 6B // MTrk，MIDI音轨开始
			*	00 00 12 17 // 此音轨长度为0x1217（4631）字节
			*	...... // 省略一些事件
			*	00 // delta-time
			*	FF // meta-event标志
			*	51 03 // 四分音符的时长（单位为微秒）
			*	07 27 0E // 四分音符的时长=0x07270E（468750）微秒
			*****************************************************************/
			public void AnalysisFile()
			{
				_header = getBytesString(4);
				_header_length = getBytesInteger(4);
				_midi_format = getBytesInteger(2);
				_track_count = getBytesInteger(2);

				if (_track_count > 0) {
					_track_header = new string[_track_count];
					_track_length = new int[_track_count];
					_track_data = new byte[_track_count][];

					_ticks_per_quarter_note = getBytesInteger(2);
					_track_header[0] = getBytesString(4);
					_track_length[0] = getBytesInteger(4);

					if (_track_length[0] > 0) {
						_track_data[0] = getBytesData(_track_length[0]);
					}
				}
			}

			public void AnalysisTrackData_0(frmMain form)
			{
				
				byte[] data = _track_data[0];
				int range = 0;
				byte[] temp = null;
				
				for (int count = 0; count < data.Length; count++) {
					if (data[count] == 0xF0) {
						range = data[count + 1];
						temp = new byte[range + 2];

						Array.Copy(data, count, temp, 0, range + 2);
						count += range + 2;

						form.appendTrackDataOutput("SystemCode", BitConverter.ToString(temp));
					} else if (data[count] == 0xFF) {
						if (data[count + 1] == 0x01) {
							range = data[count + 2];

							temp = new byte[range];
							Array.Copy(data, count +3, temp, 0, range);
							form.appendListViewItem("ME[Memo]", Encoding.Default.GetString(temp));

							temp = new byte[range + 3];
							Array.Copy(data, count, temp, 0, range + 3);
							count += range + 3;
							form.appendTrackDataOutput("MetaEvent[Memo]", BitConverter.ToString(temp));
						} else if (data[count + 1] == 0x03) {
							range = data[count + 2];

							temp = new byte[range];
							Array.Copy(data, count + 3, temp, 0, range);
							form.appendListViewItem("ME[TrackName]", Encoding.Default.GetString(temp));

							temp = new byte[range + 3];
							Array.Copy(data, count, temp, 0, range + 3);
							count += range + 3;
							form.appendTrackDataOutput("MetaEvent[TrackName]", BitConverter.ToString(temp));
						} else if (data[count + 1] == 0x06) {
							range = data[count + 2];

							temp = new byte[range];
							Array.Copy(data, count + 3, temp, 0, range);
							form.appendListViewItem("ME[Marker]", Encoding.Default.GetString(temp));

							temp = new byte[range + 3];
							Array.Copy(data, count, temp, 0, range + 3);
							count += range + 3;
							form.appendTrackDataOutput("MetaEvent[Marker]", BitConverter.ToString(temp));
						} else if (data[count + 1] == 0x51) {
							range = data[count + 2];

							temp = new byte[range];
							Array.Copy(data, count + 3, temp, 0, range);
							form.appendListViewItem("ME[Speed]", getBytesInteger(temp).ToString());
							form.appendListViewItem("Tempo", (60000000 / getBytesInteger(temp)).ToString());

							temp = new byte[range + 3];
							Array.Copy(data, count, temp, 0, range + 3);
							count += range + 3;
							form.appendTrackDataOutput("MetaEvent[Speed]", BitConverter.ToString(temp));
						} else if (data[count + 1] == 0x58) {
							range = data[count + 2];

							temp = new byte[range];
							Array.Copy(data, count + 3, temp, 0, range);
							int nn, dd, cc, bb;
							nn = temp[0]; //拍子记号，分子 
							dd = temp[1]; //拍子记号，分母表示为 2 的（dd次）冥
							cc = temp[2]; //每个 MIDI 时钟节拍器的 tick 数目
							bb = temp[3]; //24个MIDI时钟中1/32音符的数目（8是标准的）
							form.appendListViewItem("ME[TempoMarker]", nn.ToString() + "/" + Math.Pow(2, dd).ToString());
							form.appendListViewItem("ME[TM][TickCount]", cc.ToString());

							temp = new byte[range + 3];
							Array.Copy(data, count, temp, 0, range + 3);
							count += range + 3;
							form.appendTrackDataOutput("MetaEvent[TempoMarker]", BitConverter.ToString(temp));
						} else if (data[count + 1] == 0x2F && data[count +2] == 0x00) {
							range = 3;
							temp = new byte[range];
							Array.Copy(data, count, temp, 0, range);
							form.appendListViewItem("ME[TrackEnded]");
							form.appendTrackDataOutput("MetaData[TrackEnded]", BitConverter.ToString(temp));
						}
					} else if (isTurnOn(data[count])) {
						form.appendTrackDataOutput("Note ON", string.Format("{{note: {0}, speed: {1}}}", getNotes(data[count + 1]), data[count + 2]));
						form.appendNoteDataOutput(getNotes(data[count + 1]), data[count + 2]);

						count += 2;
					//} else if (isTurnOff(data[count])) {
						//form.appendTrackDataOutput("Note OFF", string.Format("{{note: {0}, speed: {1}}}", getNotes(data[count + 1]), data[count + 2]));

						//count += 2;
					//} else if (isDeltaTime(data[count])) {
					//	form.appendNoteDataOutput("R:", data[count]);
					}
				}
			}

			private string getNotes(int value)
			{
				// 音符，音阶
				int note, scale;

				note = value % 12;
				scale = value / 12 -1;

				return string.Format("{0}{1}", NOTES[note], scale);
			}

			private bool isTurnOn(int value)
			{
				return Convert.ToString(value, 16).StartsWith("9");
			}

			private bool isTurnOff(int value)
			{
				return Convert.ToString(value, 16).StartsWith("8");
			}

			private bool isDeltaTime(int value)
			{
				//return Convert.ToString(value, 2).PadLeft(8, '0').StartsWith("0");
				return ((value & 128) == 128);
			}

			private int getBytesInteger(int count)
			{
				byte[] data = reader.ReadBytes(count);

				if (BitConverter.IsLittleEndian) {
					Array.Reverse(data);
				}

				return BitConverter.ToInt16(data, 0);
			}

			private int getBytesInteger(byte[] data)
			{
				string hex = BitConverter.ToString(data).Replace("-", "");

				return Convert.ToInt32(hex, 16);
			}

			private string getBytesString(int count)
			{
				byte[] data = reader.ReadBytes(count);

				return Encoding.ASCII.GetString(data);
			}

			private byte[] getBytesData(int count)
			{
				return reader.ReadBytes(count);
			}
		}
	}
}
